From c557f7b7c4f6309efdc105073355a405d6136561 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 30 May 2010 02:15:40 -0400 Subject: [PATCH] Improved icon view keynav Use ::keynav-failed for arrow navigation in icon views, so that it is possible to override error handling. Also add API to get the row/col of an item. With this, it is possible to make arrow keynav span adjacent icon views, which is desired in the new control-center shell. testiconview-keynav demonstrates this. --- docs/reference/gtk/gtk3-sections.txt | 2 + gtk/gtk.symbols | 2 + gtk/gtkiconview.c | 81 +++++++- gtk/gtkiconview.h | 4 + tests/Makefile.am | 5 + tests/testiconview-keynav.c | 280 +++++++++++++++++++++++++++ 6 files changed, 370 insertions(+), 4 deletions(-) create mode 100644 tests/testiconview-keynav.c diff --git a/docs/reference/gtk/gtk3-sections.txt b/docs/reference/gtk/gtk3-sections.txt index 1f4e41d7d7..3c1c8f7484 100644 --- a/docs/reference/gtk/gtk3-sections.txt +++ b/docs/reference/gtk/gtk3-sections.txt @@ -1635,6 +1635,8 @@ gtk_icon_view_set_tooltip_cell gtk_icon_view_get_tooltip_context gtk_icon_view_set_tooltip_column gtk_icon_view_get_tooltip_column +gtk_icon_view_get_item_row +gtk_icon_view_get_item_column GtkIconViewDropPosition gtk_icon_view_enable_model_drag_source diff --git a/gtk/gtk.symbols b/gtk/gtk.symbols index cdaf5ff955..86f309f7c5 100644 --- a/gtk/gtk.symbols +++ b/gtk/gtk.symbols @@ -1722,6 +1722,8 @@ gtk_icon_view_set_tooltip_cell gtk_icon_view_get_tooltip_context gtk_icon_view_set_tooltip_column gtk_icon_view_get_tooltip_column +gtk_icon_view_get_item_row +gtk_icon_view_get_item_column #endif #endif diff --git a/gtk/gtkiconview.c b/gtk/gtkiconview.c index b7afe43e81..cdbd9cfe92 100644 --- a/gtk/gtkiconview.c +++ b/gtk/gtkiconview.c @@ -4125,7 +4125,6 @@ gtk_icon_view_move_cursor_up_down (GtkIconView *icon_view, if (!gtk_widget_keynav_failed (GTK_WIDGET (icon_view), direction)) { GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (icon_view)); - g_print ("move focus out\n"); if (toplevel) gtk_widget_child_focus (toplevel, direction == GTK_DIR_UP ? @@ -4222,10 +4221,13 @@ gtk_icon_view_move_cursor_left_right (GtkIconView *icon_view, gint cell = -1; gboolean dirty = FALSE; gint step; - + GtkDirectionType direction; + if (!gtk_widget_has_focus (GTK_WIDGET (icon_view))) return; - + + direction = count < 0 ? GTK_DIR_LEFT : GTK_DIR_RIGHT; + if (!icon_view->priv->cursor_item) { GList *list; @@ -4257,7 +4259,16 @@ gtk_icon_view_move_cursor_left_right (GtkIconView *icon_view, if (!item) { - gtk_widget_error_bell (GTK_WIDGET (icon_view)); + if (!gtk_widget_keynav_failed (GTK_WIDGET (icon_view), direction)) + { + GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (icon_view)); + if (toplevel) + gtk_widget_child_focus (toplevel, + direction == GTK_DIR_LEFT ? + GTK_DIR_TAB_BACKWARD : + GTK_DIR_TAB_FORWARD); + } + return; } @@ -5931,6 +5942,68 @@ gtk_icon_view_path_is_selected (GtkIconView *icon_view, return item->selected; } +/** + * gtk_icon_view_get_item_row: + * @icon_view: a #GtkIconView + * @path: the #GtkTreePath of the item + * + * Gets the row in which the item @path is currently + * displayed. Row numbers start at 0. + * + * Returns: The row in which the item is displayed + * + * Since: 2.22 + */ +gint +gtk_icon_view_get_item_row (GtkIconView *icon_view, + GtkTreePath *path) +{ + GtkIconViewItem *item; + + g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), FALSE); + g_return_val_if_fail (icon_view->priv->model != NULL, FALSE); + g_return_val_if_fail (path != NULL, FALSE); + + item = g_list_nth_data (icon_view->priv->items, + gtk_tree_path_get_indices(path)[0]); + + if (!item) + return -1; + + return item->row; +} + +/** + * gtk_icon_view_get_item_column: + * @icon_view: a #GtkIconView + * @path: the #GtkTreePath of the item + * + * Gets the column in which the item @path is currently + * displayed. Column numbers start at 0. + * + * Returns: The column in which the item is displayed + * + * Since: 2.22 + */ +gint +gtk_icon_view_get_item_column (GtkIconView *icon_view, + GtkTreePath *path) +{ + GtkIconViewItem *item; + + g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), FALSE); + g_return_val_if_fail (icon_view->priv->model != NULL, FALSE); + g_return_val_if_fail (path != NULL, FALSE); + + item = g_list_nth_data (icon_view->priv->items, + gtk_tree_path_get_indices(path)[0]); + + if (!item) + return -1; + + return item->col; +} + /** * gtk_icon_view_item_activated: * @icon_view: A #GtkIconView diff --git a/gtk/gtkiconview.h b/gtk/gtkiconview.h index 10e17897d8..422b51a385 100644 --- a/gtk/gtkiconview.h +++ b/gtk/gtkiconview.h @@ -154,6 +154,10 @@ void gtk_icon_view_unselect_path (GtkIconView *icon_ GtkTreePath *path); gboolean gtk_icon_view_path_is_selected (GtkIconView *icon_view, GtkTreePath *path); +gint gtk_icon_view_get_item_row (GtkIconView *icon_view, + GtkTreePath *path); +gint gtk_icon_view_get_item_column (GtkIconView *icon_view, + GtkTreePath *path); GList *gtk_icon_view_get_selected_items (GtkIconView *icon_view); void gtk_icon_view_select_all (GtkIconView *icon_view); void gtk_icon_view_unselect_all (GtkIconView *icon_view); diff --git a/tests/Makefile.am b/tests/Makefile.am index 526c1f6e1b..df80043ca0 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -49,6 +49,7 @@ noinst_PROGRAMS = $(TEST_PROGS) \ testframe \ testgtk \ testiconview \ + testiconview-keynav \ testicontheme \ testimage \ testinput \ @@ -196,6 +197,7 @@ testfilechooserbutton_LDADD = $(LDADDS) testgtk_LDADD = $(LDADDS) testicontheme_LDADD = $(LDADDS) testiconview_LDADD = $(LDADDS) +testiconview_keynav_LDADD = $(LDADDS) testinput_LDADD = $(LDADDS) testimage_LDADD = $(LDADDS) testmenubars_LDADD = $(LDADDS) @@ -321,6 +323,9 @@ testiconview_SOURCES = \ testiconview.c \ prop-editor.c +testiconview__keynav_SOURCES = \ + testiconview-keynav.c + testrecentchooser_SOURCES = \ prop-editor.c \ testrecentchooser.c diff --git a/tests/testiconview-keynav.c b/tests/testiconview-keynav.c new file mode 100644 index 0000000000..367a227491 --- /dev/null +++ b/tests/testiconview-keynav.c @@ -0,0 +1,280 @@ +/* testiconview-keynav.c + * Copyright (C) 2010 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Matthias Clasen + */ + +/* + * This example demonstrates how to use the keynav-failed signal to + * extend arrow keynav over adjacent icon views. This can be used when + * grouping items. + */ + +#include + +static GtkTreeModel * +get_model (void) +{ + static GtkListStore *store; + GtkTreeIter iter; + + if (store) + return (GtkTreeModel *) g_object_ref (store); + + store = gtk_list_store_new (1, G_TYPE_STRING); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, "One", -1); + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, "Two", -1); + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, "Three", -1); + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, "Four", -1); + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, "Five", -1); + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, "Six", -1); + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, "Seven", -1); + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, "Eight", -1); + + return (GtkTreeModel *) store; +} + +static gboolean +visible_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + gboolean first = GPOINTER_TO_INT (data); + gboolean visible; + GtkTreePath *path; + + path = gtk_tree_model_get_path (model, iter); + + if (gtk_tree_path_get_indices (path)[0] < 4) + visible = first; + else + visible = !first; + + gtk_tree_path_free (path); + + return visible; +} + +GtkTreeModel * +get_filter_model (gboolean first) +{ + GtkTreeModelFilter *model; + + model = (GtkTreeModelFilter *)gtk_tree_model_filter_new (get_model (), NULL); + + gtk_tree_model_filter_set_visible_func (model, visible_func, GINT_TO_POINTER (first), NULL); + + return (GtkTreeModel *) model; +} + +static GtkWidget * +get_view (gboolean first) +{ + GtkWidget *view; + + view = gtk_icon_view_new_with_model (get_filter_model (first)); + gtk_icon_view_set_text_column (GTK_ICON_VIEW (view), 0); + gtk_widget_set_size_request (view, 0, -1); + + return view; +} + +typedef struct +{ + GtkWidget *header1; + GtkWidget *view1; + GtkWidget *header2; + GtkWidget *view2; +} Views; + +static gboolean +keynav_failed (GtkWidget *view, + GtkDirectionType direction, + Views *views) +{ + GtkTreePath *path; + GtkTreeModel *model; + GtkTreeIter iter; + gint col; + GtkTreePath *sel; + + if (view == views->view1 && direction == GTK_DIR_DOWN) + { + if (gtk_icon_view_get_cursor (GTK_ICON_VIEW (views->view1), &path, NULL)) + { + col = gtk_icon_view_get_item_column (GTK_ICON_VIEW (views->view1), path); + gtk_tree_path_free (path); + + sel = NULL; + model = gtk_icon_view_get_model (GTK_ICON_VIEW (views->view2)); + gtk_tree_model_get_iter_first (model, &iter); + do { + path = gtk_tree_model_get_path (model, &iter); + if (gtk_icon_view_get_item_column (GTK_ICON_VIEW (views->view2), path) == col) + { + sel = path; + break; + } + } while (gtk_tree_model_iter_next (model, &iter)); + + gtk_icon_view_set_cursor (GTK_ICON_VIEW (views->view2), sel, NULL, FALSE); + gtk_tree_path_free (sel); + } + gtk_widget_grab_focus (views->view2); + return TRUE; + } + + if (view == views->view2 && direction == GTK_DIR_UP) + { + if (gtk_icon_view_get_cursor (GTK_ICON_VIEW (views->view2), &path, NULL)) + { + col = gtk_icon_view_get_item_column (GTK_ICON_VIEW (views->view2), path); + gtk_tree_path_free (path); + + sel = NULL; + model = gtk_icon_view_get_model (GTK_ICON_VIEW (views->view1)); + gtk_tree_model_get_iter_first (model, &iter); + do { + path = gtk_tree_model_get_path (model, &iter); + if (gtk_icon_view_get_item_column (GTK_ICON_VIEW (views->view1), path) == col) + { + if (sel) + gtk_tree_path_free (sel); + sel = path; + } + else + gtk_tree_path_free (path); + } while (gtk_tree_model_iter_next (model, &iter)); + + gtk_icon_view_set_cursor (GTK_ICON_VIEW (views->view1), sel, NULL, FALSE); + gtk_tree_path_free (sel); + } + gtk_widget_grab_focus (views->view1); + return TRUE; + } + + return FALSE; +} + +static gboolean +focus_out (GtkWidget *view, + GdkEventFocus *event, + gpointer data) +{ + gtk_icon_view_unselect_all (GTK_ICON_VIEW (view)); + + return FALSE; +} + +static gboolean +focus_in (GtkWidget *view, + GdkEventFocus *event, + gpointer data) +{ + GtkTreePath *path; + + if (!gtk_icon_view_get_cursor (GTK_ICON_VIEW (view), &path, NULL)) + { + path = gtk_tree_path_new_from_indices (0, -1); + gtk_icon_view_set_cursor (GTK_ICON_VIEW (view), path, NULL, FALSE); + } + + gtk_icon_view_select_path (GTK_ICON_VIEW (view), path); + gtk_tree_path_free (path); + + return FALSE; +} + +static void +header_style_set (GtkWidget *widget, + GtkStyle *old_style) +{ + g_signal_handlers_block_by_func (widget, header_style_set, NULL); + gtk_widget_modify_bg (widget, GTK_STATE_NORMAL, + &widget->style->base[GTK_STATE_NORMAL]); + gtk_widget_modify_fg (widget, GTK_STATE_NORMAL, + &widget->style->text[GTK_STATE_NORMAL]); + g_signal_handlers_unblock_by_func (widget, header_style_set, NULL); +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *vbox; + Views views; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 0); + gtk_container_add (GTK_CONTAINER (window), vbox); + + views.header1 = g_object_new (GTK_TYPE_LABEL, + "label", "Group 1", + "use-markup", TRUE, + "xalign", 0.0, + NULL); + views.view1 = get_view (TRUE); + views.header2 = g_object_new (GTK_TYPE_LABEL, + "label", "Group 2", + "use-markup", TRUE, + "xalign", 0.0, + NULL); + views.view2 = get_view (FALSE); + + g_signal_connect (views.view1, "keynav-failed", + G_CALLBACK (keynav_failed), &views); + g_signal_connect (views.view2, "keynav-failed", + G_CALLBACK (keynav_failed), &views); + g_signal_connect (views.view1, "focus-in-event", + G_CALLBACK (focus_in), NULL); + g_signal_connect (views.view1, "focus-out-event", + G_CALLBACK (focus_out), NULL); + g_signal_connect (views.view2, "focus-in-event", + G_CALLBACK (focus_in), NULL); + g_signal_connect (views.view2, "focus-out-event", + G_CALLBACK (focus_out), NULL); + g_signal_connect (views.header1, "style-set", + G_CALLBACK (header_style_set), NULL); + g_signal_connect (views.header2, "style-set", + G_CALLBACK (header_style_set), NULL); + g_signal_connect (window, "style-set", + G_CALLBACK (header_style_set), NULL); + + gtk_container_add (GTK_CONTAINER (vbox), views.header1); + gtk_container_add (GTK_CONTAINER (vbox), views.view1); + gtk_container_add (GTK_CONTAINER (vbox), views.header2); + gtk_container_add (GTK_CONTAINER (vbox), views.view2); + + gtk_widget_show_all (window); + + gtk_main (); + + return 0; +} + -- 2.30.2